package org.NooLab.glue.net.sockets.udp;

 
import java.io.*;
import java.net.*;
import java.util.* ;
  
import org.NooLab.utilities.logging.PrintLog;
  
  

public class UdpObjects extends Observable {

	DatagramSocket dsocketOut; 
		
	String ip_address;
	String[] ip_addresses ;
	
	int clientPort ;
	int maxBufferSize = 165536;
	
	InetAddress inetAddr;
	InetAddress[] inetAddrs;
	
	RecThread recthread;
	int connectionTimeOutValue = 8000;
	
	int inboundSocket = 0 ;
	
	boolean connectionShutDown=false;
	boolean isExceptionVisible=true;
	
	boolean running;           // Is the thread running?  Yes or no?
	int wait;                  // How many milliseconds should we wait in between executions?
	String id="";              // Thread name
	int count;                 // counter
	Integer[] clientPorts ;
	
	int lastState = -1;
	String description = "";
	Observer observingObject;
	public boolean objectavailable ;

	PrintLog out = new PrintLog(2,true);
	
	// server object
	public UdpObjects(int port, String ...ipaddrs){
		clientPort = port;
	 
		
		int p = ipaddrs.length ;
		ip_addresses = new String[p] ;
		if (p>0){
			ip_address = ipaddrs[0];
		}
		
		for (int i=0;i<p;i++){
			ip_addresses[i] = ipaddrs[i] ;	
		}
		
		
		try{
			inetAddrs =new InetAddress[p];
			int z=-1;
			for (String ip: ip_addresses){
				z++;
				inetAddrs[z] = InetAddress.getByName(ip);
			}
			inetAddr = inetAddrs[0] ;
			
			//srvsock = new ServerSocket(clientPort);
			dsocketOut = new DatagramSocket();
			
			
		} catch(SocketException e){
		} catch(UnknownHostException e){
			e.printStackTrace();
			return;
		}		
	}

	
	// client object
	public UdpObjects(int port, Observer observer, String description){
		
		clientPort = port ;
		
		// the observer is set to the MessageBoard!
		if (observer!=null){ // ==null if we test the connection!!
			addObserver(observer) ;
		}
		
		if (observer == null){
			isExceptionVisible = false;
		}
		observingObject = observer;
		recthread = new RecThread(5, "1", description );
		if (recthread.rthr==null){
			recthread = null;
		}
		
		
	}
	
	@SuppressWarnings("static-access")
	public void close(){

		try {
			if (dsocketOut != null) {
				
				try {
					if (dsocketOut.isBound()) {
						//dsocketOut.setSoTimeout(100);
						dsocketOut.setReuseAddress(true);
					}

				} catch (Exception e) {
				}
				
				if (dsocketOut.isConnected()) {
					dsocketOut.disconnect();
				}

				if (dsocketOut.isClosed() == false) {
					while  (dsocketOut.isClosed() == false){
						// dsocketOut.setReuseAddress(true);
						dsocketOut.close();
						Thread.currentThread().sleep(10);
					}
				}
				Thread.currentThread().sleep(105);
				//dsocketOut = null;
			}
		} catch (Exception e) {
		}

		if (recthread!=null){
			recthread.close();
		}
		
	}
	
	public void start( String description ){
		recthread.startReceiver(  );
	}
	
	protected void returnObject( Object obj ){
		
		// here we call the callback !
												out.print(4, "         ----> returning object to ----> "+observingObject.toString());
		
		setChanged();
		this.notifyObservers( obj ) ;
	}
	
	public void broadcastAll( byte[] dataobjBytes ){
		
		for (InetAddress inet: inetAddrs){ 
			_broadcast( dataobjBytes, inet );
		}
		
	}
	  

	public void broadcast( byte[] dataobjBytes, InetAddress inet ){
		_broadcast( dataobjBytes, inetAddr );
	}
	
	
	public void broadcast( Object obj ) {
	
		broadcast( obj, inetAddr);
	}
	
	private void broadcast( Object obj, InetAddress inet){
		
		int pL  ;
		
		BufferedOutputStream bos;
		ObjectOutputStream oos ;
		ByteArrayOutputStream baStream ;
		
		DatagramPacket dgp ;
		byte[] packet;
		
		
		// turn the object into some encoding ? Base64 in order to transfer ASCII only... ???
	
		
		 
		try {
			// Need these output streams to get image as bytes for UDP communication
			lastState = -1;
			
			baStream = new ByteArrayOutputStream();
			bos = new BufferedOutputStream(baStream);
			oos = new ObjectOutputStream(bos);

			lastState = -2;
			oos.writeObject(obj);
		     
		    oos.flush();
		    
		    lastState = -3;
		    oos.close(); 
		    bos.close();
		    baStream.close();
		    
		    lastState = -4;
		    packet = baStream.toByteArray ();
		    
			// Get the byte array, which we will send out via UDP!
			 

			// Send data as a datagram
			// print("Sending datagram with " + packet.length + " bytes...");

			pL = packet.length;
			 

			lastState = -5;															
			dgp = new DatagramPacket( packet, pL, inet, clientPort );
			dsocketOut.send(dgp);
											// out.print(2,""+pL+" bytes sent to port "+cP+ " @ "+inet );
			// println("ok.");
			lastState = 0;
			dgp = null;
			
		} catch (Exception e) {
			lastState = -17;
			e.printStackTrace();
		}
	  
	}
	
	// method to broadcast a byte array over UDP
	private void _broadcast( byte[] data, InetAddress inet ) {
		
		DatagramPacket dgp ;
		byte[] packet;
		
		
		// turn the object into some encoding ? Base64 in order to transfer ASCII only... ???
	
		// Get the byte array, which we will send out via UDP!
		packet = data.clone() ;

		// Send data as a datagram
		// print("Sending datagram with " + packet.length + " bytes...");
	  
		try {
			int pL = packet.length;  
			 
		
			dgp = new DatagramPacket(packet,pL, inet ,clientPort);  
			dsocketOut.send(dgp);
	    
			// println("ok.");
		}catch (Exception e) {
			e.printStackTrace();
		}
	  
		 
	}
	
	  

	// inner class for receiving data in threaded mode
	 


	public void setMaxBufferSize(int maxBufferSize) {
		this.maxBufferSize = maxBufferSize;
	}


	public int getLastState() {
		return lastState;
	}




	public void setExceptionVisible(boolean isExceptionVisible) {
		this.isExceptionVisible = isExceptionVisible;
	}




	public int getInboundSocket() {
		return inboundSocket;
	}




	public int getConnectionTimeOutValue() {
		return connectionTimeOutValue;
	}


	public void setConnectionTimeOutValue(int connectionTimeOutValue) {
		this.connectionTimeOutValue = connectionTimeOutValue;
	}




	class RecThread  implements Runnable{
	
		byte[] buffer ;
		
		boolean successfullyStopped = false;
		boolean running = false;   // Is the thread running?  Yes or no?
		boolean activated = false;
		
		int wait = 5 ;            // How many milliseconds should we wait in between executions?
		String id = "";            // Thread name
		int count = 0;             // counter
		int state = 0;
		
		String description="";
		Thread rthr;
		
		DatagramSocket datagramSocketIn; 	
		ServerSocket srvsock; 
		
		
		public RecThread (int w, String s , String descr ) {
		    wait = w;
		    running = false;
		    id = s;
		    count = 0;
		    
		    description = descr;
		    
		    
		    try{
		    	
		    	buffer = new byte[ maxBufferSize ];
		    	
		    	
		    	// that's the really nasty trick: define a serversocket to the same port as the  DatagramSocket, 
		    	// otherwise it is not possible to "unbind" the port via setReuseAddress(true) !!
		    		inboundSocket = 1;
		    	srvsock = new ServerSocket(clientPort);
		    	srvsock.setReuseAddress(true);
		    	srvsock.setSoTimeout( connectionTimeOutValue ) ;
		    		inboundSocket = 2;
		    		
		    	datagramSocketIn = new DatagramSocket( clientPort );
		    	datagramSocketIn.setReuseAddress(true);
		    	
		    	if ((datagramSocketIn.isBound()) && (datagramSocketIn.isClosed()==false)){
		    		inboundSocket = 9;
		    	}
		    	// sockadr = datagramSocketIn.getLocalSocketAddress() ; // e..g.:  0.0.0.0/0.0.0.0:7100
		    	
		    	// datagramSocketIn.bind(sockadr) ;
		    	// datagramSocketIn.setSoTimeout(5000);
		    	// a timeout causes a re-receive (reloading the buffer) every <n> milliseconds !!!
		    	
		    	
		    	if (datagramSocketIn.getReuseAddress()==false){
		    		System.out.println("Socket ReuseAddress is not possible on this machine.");
		    	}
				
		    } catch(SocketException se){
		    	inboundSocket = -7 ;
		    	state=-7;
		    	if (isExceptionVisible){
		    		System.err.println("\nAttempt to open port ["+clientPort+"]  failed ("+description+").\n");
		    	}
		    	// se.printStackTrace();
		    	
		    } catch (IOException e) {
		    	inboundSocket = -9 ;
		    	state=-9;
				// e.printStackTrace();
			}catch(Exception e){
				inboundSocket = -11 ;
				state=-11;
		    	System.out.println("\nProblems to open port ["+clientPort+"].\n");
		    	e.printStackTrace();
			}

			if (state==0){
				rthr = new Thread(this);
				rthr.start() ;
			}else{
				rthr= null;
			}
		}

		@SuppressWarnings("static-access")
		public void close(){
			int z;
			
			
			try{
			
				z=0;
				while ((running == true ) &&(z<100)){
					running = false;
					try{
						Thread.currentThread().sleep(50);
					}catch(Exception e){}
					z++;
				}
			 

				if ((rthr!=null) && (rthr.isAlive())) {
					rthr.interrupt();
					try{
						Thread.currentThread().sleep(150);
						if (running){
							rthr.join(200);
						}
					}catch(Exception e){}
					
				}

				// if ((rthr.isAlive()) || (running == true)) {
				if ( (running == true)) { // successfullyStopped==false) ||
					if (isExceptionVisible){
						System.out.println("shutting down receiver thread on port "+clientPort+" failed!") ;
					}
//if (clientPort==7001)
{
	clientPort=clientPort+0;
}
				} // still running ?
				
				rthr = null;
				try {
					
					connectionShutDown=true;
					if ( datagramSocketIn != null){
						
						// datagramSocketIn.setSoTimeout(10);
						// datagramSocketIn.bind(null) ;
						
						if (datagramSocketIn.isConnected()){
							datagramSocketIn.disconnect();	
						}
					
						if  (datagramSocketIn.isClosed() == false) {
							// datagramSocketIn.setReuseAddress(true);
							datagramSocketIn.close();
							Thread.currentThread().sleep(50);
						}
						
						datagramSocketIn = null;
					}
					
					if (srvsock != null) {
						try {
							srvsock.close();
						} catch (IOException e) {
							/* should not be thrown */
						}
					}
				} catch(InterruptedException iex){
					
				} catch (Exception e) {
					e.printStackTrace();
				}

			} catch (Exception e) {
				e.printStackTrace();
			}

		}
		
		
		
		public void setPeriod(int _wait){
			wait = _wait;
		}
		
		
		 
		// this is just a "virtual" start!
		public void startReceiver ( ) {
			
			
		    // Print messages
		    // println("Starting receiving thread (minimal delay is " + wait + " milliseconds.)"); 
		     
			activated = true ;
		    
		}
		 
		   
		@SuppressWarnings("static-access")
		public void run () {
			running = true;
			Object obj = null ;
			objectavailable = false;
			
			try {
				while (running) {
					// Ok, let's wait for however long we should wait

					if (activated) {
						// Ok, let's wait for however long we should wait
						rthr.sleep((long) (wait));
						// rthr.sleep((2000));
						
						
						if (objectavailable == false) {
							out.print(4, "    ... receiver is waiting");
							// this is actually
							obj = loadAvailableData();
						}
						
						if (obj != null){
							out.print(4, "    ... data received !!! ");
							returnObject(obj) ; // this returns the data to the observer 
							objectavailable = false;
							// running = false;
						}
						
					} else {
						rthr.sleep(0, 200);
					}
				} // while running
				// System.out.println(id + " thread is done!");
				// The thread is done when we get to the end of run()
			} catch(InterruptedException iex){
				// educated silence
			} catch (Exception e) {
				e.printStackTrace() ;
			}  
			
			activated = false;
			successfullyStopped = true;
		}


		 
		// Our method that quits the thread
		@SuppressWarnings("static-access")
		public void quit() {
		    // System.out.println("Quitting."); 
		    
			// if the run() loop is accessible ... this will exit it...
		    running = false;  
		    activated = false ;
		    // we wait a moment, then we interrupt it
		    try{
		    	rthr.sleep(50);
		    	rthr.interrupt();
		    } catch (Exception e) {
				e.printStackTrace() ;
		    }
		    
		}

		public Object loadAvailableData(){
			
			Object transferredObj = null;
			
			int dataSize;
			boolean _avail=false;
			objectavailable = false;
			DatagramPacket dpacket = null ;
			byte[] data = null ;
			
			ObjectInputStream ois ;
			ByteArrayInputStream bais ;
			
			try {
				
				dpacket = new DatagramPacket(buffer, buffer.length-1); 
				_avail = false;
				
			    // 
				if ( datagramSocketIn!=null ){
					datagramSocketIn.receive( dpacket );
				}else{
					return null ;
				}
			    
				
				data = new byte[0];
				dataSize = dpacket.getLength() ;

				if (dataSize>0){
					data = Arrays.copyOf( dpacket.getData(), dataSize) ;
				}

			} catch(SocketException sex){
				if (connectionShutDown==false){
					sex.printStackTrace();
				}
				return null;
			} catch(SocketTimeoutException sex){
				if (connectionShutDown==false){
					sex.printStackTrace();
				}
				return null;
			} catch (IOException e) {
				e.printStackTrace();
			}
			
											if ((data!=null) && (data.length>0)){
												out.print(4, "    :: data ["+data.length+"]::  "+ data.toString());
											}
			try {
		
				// Read incoming data into a ByteArrayInputStream
				  
			    bais = new ByteArrayInputStream (data);
			    ois = new ObjectInputStream (bais);
			    
			    transferredObj = ois.readObject();
				
				// read the encoded object from the stream
				// BufferedImage img = ImageIO.read(bais);
		
				// and acquire it to the local system (Observer pattern)
		
				_avail = true;
		
			}catch(IOException iex){
				
			} catch (Exception e) {
			    e.printStackTrace();
			    _avail = false;
			}
			finally{
				if (dpacket!=null){
					dpacket.setLength(0);
				}
			}

			if ( _avail == true){
				objectavailable =_avail ;
				// Arrays.fill( buffer, 0, buffer.length-1, (byte)0);
				
			}
			return transferredObj ; 
		}

 
	} // inner threaded class
	 	
}



